package space.edge.d.demo.qrcode.privatecode.decoder;

import space.edge.d.demo.qrcode.common.DecodeHintType;
import space.edge.d.demo.qrcode.common.DecoderResult;
import space.edge.d.demo.qrcode.exception.ChecksumException;
import space.edge.d.demo.qrcode.exception.FormatException;
import space.edge.d.demo.qrcode.common.BitMatrix;
import space.edge.d.demo.qrcode.common.reedsolomon.GenericGF;
import space.edge.d.demo.qrcode.common.reedsolomon.ReedSolomonDecoder;
import space.edge.d.demo.qrcode.common.reedsolomon.ReedSolomonException;
import space.edge.d.demo.qrcode.common.ErrorCorrectionLevel;

import java.util.Map;

/**
 * Created by gsl on 2017/10/16.
 * 解码
 */

public class Decoder{

    private final ReedSolomonDecoder decoder;

    public Decoder() {
        decoder = new ReedSolomonDecoder(GenericGF.QR_CODE_FIELD_256);
    }

    public DecoderResult decode(BitMatrix bitMatrix, Map<DecodeHintType, ?> hints) throws FormatException, ChecksumException {
        BitMatrixParser parser = new BitMatrixParser(bitMatrix);
        FormatException fe = null;
        ChecksumException ce = null;
        try {
            return decode(parser, hints);
        } catch (FormatException e) {
            fe = e;
        } catch (ChecksumException e) {
            ce = e;
        }

        try {
            parser.readVersion();
            parser.readFormatInformation();
            return decode(parser, hints);
        } catch (FormatException | ChecksumException e) {
            if (fe != null)
                throw fe;
            throw ce;
        }
    }

    private DecoderResult decode(BitMatrixParser parser, Map<DecodeHintType, ?> hints) throws FormatException, ChecksumException {
        Version version = parser.readVersion();
        ErrorCorrectionLevel ecLevel = parser.readFormatInformation().getErrorCorrectionLevel();

        byte[] codewords = parser.readCodeWords();
        DataBlock[] dataBlocks = DataBlock.getDataBlocks(codewords, version, ecLevel);

        int totalBytes = 0;
        for (DataBlock block : dataBlocks) {
            totalBytes += block.getNumDataCodewords();
        }
        byte[] resultBytes = new byte[totalBytes];
        int resultOffset = 0;

        for (DataBlock dataBlock : dataBlocks) {
            byte[] codewordBytes = dataBlock.getCodewords();
            int numDataCodewords = dataBlock.getNumDataCodewords();
            correctErrors(codewordBytes, numDataCodewords);
            for (int i = 0; i < numDataCodewords; i++) {
                resultBytes[resultOffset++] = codewordBytes[i];
            }
        }
        return DecodedBitStreamParser.decode(resultBytes, version, ecLevel, hints);
    }

    /**
     * <p>Given data and error-correction codewords received, possibly corrupted by errors, attempts to
     * correct the errors in-place using Reed-Solomon error correction.</p>
     *
     * @param codewordBytes    data and error correction codewords
     * @param numDataCodewords number of codewords that are data bytes
     * @throws ChecksumException if error correction fails
     */
    private void correctErrors(byte[] codewordBytes, int numDataCodewords) throws ChecksumException {
        int numCodewords = codewordBytes.length;
        // First read into an array of ints
        int[] codewordsInts = new int[numCodewords];
        for (int i = 0; i < numCodewords; i++) {
            codewordsInts[i] = codewordBytes[i] & 0xFF;
        }
        try {
            decoder.decode(codewordsInts, codewordBytes.length - numDataCodewords);
        } catch (ReedSolomonException ignored) {
            throw ChecksumException.getChecksumInstance();
        }
        // Copy back into array of bytes -- only need to worry about the bytes that were data
        // We don't care about errors in the error-correction codewords
        for (int i = 0; i < numDataCodewords; i++) {
            codewordBytes[i] = (byte) codewordsInts[i];
        }
    }


//    private List<Point> pickPoint(int radius, BitMatrix bitMatrix) {
//        BitArray bits = new BitArray();
//        int centerX = bitMatrix.getWidth() / 2;
//        int centerY = bitMatrix.getHeight() / 2;
//        List<Point> points = bresenhamCircle(centerX, centerY, radius, bitMatrix, bits);
//        int segSize = bits.getSize() / 8;
//        BitArray finalBits = new BitArray();
//        for (int i = 0; i < 8; i++) {
//            if (i % 2 == 0) {
//                for (int j = 0; j < segSize; j++) {
//                    finalBits.appendBit(bits.get(i + j * 8));
//                }
//            } else {
//                for (int j = segSize - 1; j >= 0; j--) {
//                    finalBits.appendBit(bits.get(i + j * 8));
//                }
//            }
//        }
//        int size = finalBits.getSize();
//        return points;
//    }
//
//    private List<Point> bresenhamCircle(int centerX, int centerY, int raidus, BitMatrix bitMatrix, BitArray bitArray) {
//        List<Point> points = new ArrayList<>();
//        int x = 0;
//        int y = raidus;
//        int d = 3 - 2 * raidus;
//        circlePlot(centerX, centerY, x, y, bitMatrix, bitArray, points);
//        while (x <= y) {
//            if (d < 0) {
//                d = d + 4 * x + 6;
//            } else {
//                d = d + 4 * (x - y) + 10;
//                y--;
//            }
//            x++;
//            circlePlot(centerX, centerY, x, y, bitMatrix, bitArray, points);
//        }
//        return points;
//    }
//
//    int count = 1;
//
//    private void circlePlot(int centerX, int centerY, int x, int y, BitMatrix bitMatrix, BitArray bits, List<Point> points) {
//        bits.appendBit(bitMatrix.get(centerX + x, centerY - y));
//        bits.appendBit(bitMatrix.get(centerX + y, centerY - x));
//        bits.appendBit(bitMatrix.get(centerX + y, centerY + x));
//        bits.appendBit(bitMatrix.get(centerX + x, centerY + y));
//        bits.appendBit(bitMatrix.get(centerX - x, centerY + y));
//        bits.appendBit(bitMatrix.get(centerX - y, centerY + x));
//        bits.appendBit(bitMatrix.get(centerX - y, centerY - x));
//        bits.appendBit(bitMatrix.get(centerX - x, centerY - y));
//
//        points.add(new Point(centerX + x, centerY - y));
//        points.add(new Point(centerX + y, centerY - x));
//        points.add(new Point(centerX + y, centerY + x));
//        points.add(new Point(centerX + x, centerY + y));
//        points.add(new Point(centerX - x, centerY + y));
//        points.add(new Point(centerX - y, centerY + x));
//        points.add(new Point(centerX - y, centerY - x));
//        points.add(new Point(centerX - x, centerY - y));
//
//        Log.d("wlp", count++ + "（" + (centerX + x) + "，" + (centerY - y) + "）");
//        Log.d("wlp", count++ + "（" + (centerX + y) + "，" + (centerY - x) + "）");
//        Log.d("wlp", count++ + "（" + (centerX + y) + "，" + (centerY + x) + "）");
//        Log.d("wlp", count++ + "（" + (centerX + x) + "，" + (centerY + y) + "）");
//        Log.d("wlp", count++ + "（" + (centerX - x) + "，" + (centerY + y) + "）");
//        Log.d("wlp", count++ + "（" + (centerX - y) + "，" + (centerY + x) + "）");
//        Log.d("wlp", count++ + "（" + (centerX - y) + "，" + (centerY - x) + "）");
//        Log.d("wlp", count++ + "（" + (centerX - x) + "，" + (centerY - y) + "）");
//    }

}
